﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace SystemHelperLibrary
{
	namespace WcfExt
	{
		using System.Diagnostics;
		using System.ServiceModel;
		using System.ServiceModel.Channels;
		using System.Data;
		using System.Runtime.Serialization;
		using System.Net;
		using System.Net.Sockets;

		public static class WcfHostFactory
		{
			struct WcfHostRecord
			{
				public WcfHostRecord(ServiceHost host, string address)
				{
					Host = host;
					Address = new EndpointAddress(address);
				}
				public readonly ServiceHost Host;
				public readonly EndpointAddress Address;
			}

			static readonly Uri BaseAddress = new Uri("net.tcp://localhost/" +
												 Guid.NewGuid().ToString());

			static readonly Binding Binding;
			static Dictionary<Type, WcfHostRecord> m_Hosts = new Dictionary<Type, WcfHostRecord>();

			static WcfHostFactory()
			{
				NetTcpBinding binding = new NetTcpBinding();
				binding.TransactionFlow = true;
				Binding = binding;
				AppDomain.CurrentDomain.ProcessExit += delegate
				{
					foreach (WcfHostRecord hostRecord in m_Hosts.Values)
					{
						hostRecord.Host.Close();
					}
				};
			}

			public static I CreateInstance<S, I>()
				where I : class
				where S : I
			{
				WcfHostRecord hostRecord = GetHostRecord<S, I>();
				return ChannelFactory<I>.CreateChannel(Binding, hostRecord.Address);
			}

			static WcfHostRecord GetHostRecord<S, I>()
				where I : class
				where S : I
			{
				WcfHostRecord hostRecord;
				if (m_Hosts.ContainsKey(typeof(S)))
				{
					hostRecord = m_Hosts[typeof(S)];
				}
				else
				{
					ServiceHost host = new ServiceHost(typeof(S), BaseAddress);
					string address = BaseAddress.ToString() + Guid.NewGuid().ToString();
					hostRecord = new WcfHostRecord(host, address);
					m_Hosts.Add(typeof(S), hostRecord);
					host.AddServiceEndpoint(typeof(I), Binding, address);
					host.Open();
				}
				return hostRecord;
			}

			public static void CloseProxy<I>(I instance) where I : class
			{
				ICommunicationObject proxy = instance as ICommunicationObject;
				Debug.Assert(proxy != null);
				proxy.Close();
			}
		}

		public static class DataTableHelper
		{
			public static T[] ToArray<R, T>(this DataTable table, Func<R, T> converter)
																		  where R : DataRow
			{
				//Verify [DataContract] or [Serializable] on T
				Debug.Assert(IsDataContract(typeof(T)) || typeof(T).IsSerializable);

				if (table.Rows.Count == 0)
				{
					return new T[] { };
				}

				//Verify table contains correct rows
				Debug.Assert(MatchingTableRow<R>(table));

				return table.Rows.Cast<R>().Select(converter).ToArray();
			}
			static bool IsDataContract(Type type)
			{
				object[] attributes =
							   type.GetCustomAttributes(typeof(DataContractAttribute), false);
				return attributes.Length == 1;
			}
			static bool MatchingTableRow<R>(DataTable table)
			{
				if (table.Rows.Count == 0)
				{
					return true;
				}
				return table.Rows[0] is R;
			}
		}

		public static class WsDualProxyHelper
		{
			public static void SetClientBaseAddress<T>(this DuplexClientBase<T> proxy,
													   int port) where T : class
			{
				WSDualHttpBinding binding = proxy.Endpoint.Binding as WSDualHttpBinding;
				Debug.Assert(binding != null);
				binding.ClientBaseAddress = new Uri("http://localhost:" + port + "/");
			}
			public static void SetClientBaseAddress<T>(this DuplexClientBase<T> proxy)
																		where T : class
			{
				lock (typeof(WsDualProxyHelper))
				{
					int portNumber = FindPort();
					SetClientBaseAddress(proxy, portNumber);
					proxy.Open();
				}
			}
			internal static int FindPort()
			{
				IPEndPoint endPoint = new IPEndPoint(IPAddress.Any, 0);
				using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream,
												 ProtocolType.Tcp))
				{
					socket.Bind(endPoint);
					IPEndPoint local = (IPEndPoint)socket.LocalEndPoint;
					return local.Port;
				}
			}
		}
	}

	namespace WcfClientExt
	{
		using System.Net;
		using System.IO;
		using System.Text.RegularExpressions;
		using System.Runtime.Remoting.Messaging;
		using System.Web;

		public class WebServiceClient
		{
			#region Delegates
			public delegate string InvokeServiceHandler();
			#endregion

			#region Enumerators
			public enum WebServiceType
			{
				Traditional = 0,
				WCF = 1
			}
			#endregion

			#region Classes
			public class WebServiceParameter
			{
				public string Name { get; set; }
				public string Value { get; set; }
			}
			#endregion

			#region Member Variables
			string m_soapEnvelope =
					@"<soap:Envelope
                    xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
                    xmlns:xsd='http://www.w3.org/2001/XMLSchema'
                    xmlns:soap='http://schemas.xmlsoap.org/soap/envelope/'>
                <soap:Body></soap:Body></soap:Envelope>";

			#endregion

			#region Properties
			public string Url { get; set; }

			public string WebMethod { get; set; }

			public List<WebServiceParameter> Parameters { get; set; }

			public WebServiceType ServiceType { get; set; }

			public string WCFContractName { get; set; }
			#endregion

			#region Private Methods
			private string CreateSoapEnvelope()
			{
				string MethodCall = "<" + this.WebMethod + @" xmlns=""http://tempuri.org/"">";
				string StrParameters = string.Empty;

				foreach (WebServiceParameter param in this.Parameters)
				{
					StrParameters = string.Format("{0}<{1}>{2}</{3}>", StrParameters, param.Name, param.Value, param.Name);
				}

				MethodCall = string.Format("{0}{1}</{2}>", MethodCall, StrParameters, this.WebMethod);

				StringBuilder sb = new StringBuilder(m_soapEnvelope);
				sb.Insert(sb.ToString().IndexOf("</soap:Body>"), MethodCall);

				return sb.ToString();
			}

			private HttpWebRequest CreateWebRequest()
			{
				HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(this.Url);
				if (this.ServiceType == WebServiceClient.WebServiceType.WCF)
					webRequest.Headers.Add("SOAPAction", "\"http://tempuri.org/" + this.WCFContractName + "/" + this.WebMethod + "\"");
				else
					webRequest.Headers.Add("SOAPAction", "\"http://tempuri.org/" + this.WebMethod + "\"");

				webRequest.Headers.Add("To", this.Url);

				webRequest.ContentType = "text/xml;charset=\"utf-8\"";
				webRequest.Accept = "text/xml";
				webRequest.Method = "POST";
				return webRequest;
			}

			private string StripResponse(string SoapResponse)
			{
				string RegexExtract = @"<" + this.WebMethod + "Result>(?<Result>.*?)</" + this.WebMethod + "Result>";

				return Regex.Match(SoapResponse, RegexExtract).Groups["Result"].Captures[0].Value;
			}
			#endregion

			#region Public Methods
			public void BeginInvokeService(AsyncCallback InvokeCompleted)
			{
				InvokeServiceHandler Invoke = new InvokeServiceHandler(this.InvokeService);

				IAsyncResult result = Invoke.BeginInvoke(InvokeCompleted, null);
			}

			public string EndInvokeService(IAsyncResult result)
			{
				var asyncResult = (AsyncResult)result;
				ReturnMessage msg = (ReturnMessage)asyncResult.GetReplyMessage();

				return msg.ReturnValue.ToString();
			}

			public string InvokeService()
			{
				WebResponse response = null;
				string strResponse = string.Empty;

				//Create the request
				HttpWebRequest req = this.CreateWebRequest();

				//write the soap envelope to request stream
				using (Stream stm = req.GetRequestStream())
				{
					using (StreamWriter stmw = new StreamWriter(stm))
					{
						stmw.Write(this.CreateSoapEnvelope());
					}
				}

				//get the response from the web service
				response = req.GetResponse();

				using (Stream str = response.GetResponseStream())
				{
					StreamReader sr = new StreamReader(str);
					strResponse = sr.ReadToEnd();

					return this.StripResponse(HttpUtility.HtmlDecode(strResponse));
				}
			}
			#endregion
		}
	}
}
